###*
 # @file IMD2.js - A Simple AMD-Like IIFE Module Solution
 # @author urain39 <urain39@qq.com>
 ###

# Required, but undefined
wantedModules = {}
# Defined, and be executed
definedModules = {}

__global = @

__debug = console and console.log or () -> return

__popValue =
###*
 # Remove first one which equals given value
 # from array. If not found, return `undefined`.
 # @private
 # @func
 # @param {Array} array
 # @param {*} value
 # @return {*}
 ###
(array, value) ->
	i = array.indexOf value

	if i isnt -1
		e = array[i]
		array_ = (array.slice 0, i).concat array.slice i + 1

		# Clear
		array.length = 0

		# Write back
		for i in array_
			array.push i

		e  # Return value

__hasOwnProperty = {}.hasOwnProperty

###*
 # @callback Factory
 # @return {*}
 ###

defineModule =
###*
 # @func
 # @param {string} name
 # @param {Array<string>} [requirements]
 # @param {Factory} factory
 ###
() ->
	switch arguments.length
		when 2
			name = arguments[0]
			factory = arguments[1]
		when 3
			name = arguments[0]
			requirements = arguments[1]
			factory = arguments[2]
		else
			throw new Error "Invalid arguments"

	if __hasOwnProperty.call definedModules, name
		__debug name + " has already defined."

		return

	# Check requirements
	if requirements and requirements.length
		# Remove defined requirements
		for r in requirements
			if definedModules[r]
				__popValue requirements, r

		# Have undefined requirements?
		if requirements.length
			# Yes, record undefined requirements
			for ur in requirements
				if not wantedModules[ur]
					wantedModules[ur] = []

					__debug ur + " is undefined, recorded."

				# And mark current module requires it
				wantedModules[ur].push [
					name,
					requirements,
					factory
				]

			__debug name + " wait for " + requirements + "..."

			return  # Wait requirements be defined

	# All requirements are defined, execute it
	__global[name] = definedModules[name] = factory()

	__debug "defined " + name + "."

	# Update who wanted current module
	modules = wantedModules[name]

	if modules and modules.length
		for m in modules
			requirements_ = m[1]

			# Remove current name in requirements
			__popValue requirements_, name

			# Are all requirements defined?
			if not requirements_.length
				# Yes, re-define it
				name_ = m[0]
				factory_ = m[2]

				defineModule name_, factory_

	# Next we will check `definedModules` directly
	wantedModules[name] = undefined

	return  # No return-value

defineModule "IMD", () ->
	self =
		define: defineModule
